/**
 * @copyright Copyright (c) 2025 
 * CSG Digital Grid Technology (Guangdong) Co., Ltd.
 * @author xxxcsg
 * @brief OpenHarmony 系统静态信息采集工具
 * @details 提供系统核心静态信息的采集与 JSON 格式化功能，支持一次性采集、缓存复用，核心能力如下：
 *          1. 采集信息类型：系统版本、芯片架构、设备序列号（SN）、IP 地址、CPU 核心数、内存大小（RAM）、磁盘总空间（ROM）
 *          2. 采集来源适配：
 *             - 系统版本：优先通过 GetSoftwareModel() API 获取，失败则读取 /system/etc/param/ohos_const/ohos.para 配置文件
 *             - IP 地址：优先读取 /etc/init.d/S80network 配置文件，失败则通过 ifconfig 命令解析 eth0 网卡地址
 *             - 设备序列号：优先通过 ss_mpi_sys 接口获取芯片唯一 ID，失败则读取 /sys/block/mmcblk0/device/cid
 *             - 其他信息：通过系统命令（uname、cat /proc/cpuinfo/meminfo 等）解析获取
 *          3. 缓存机制：采用读写锁保证线程安全，信息仅采集一次并缓存，避免重复IO操作
 *          4. 格式化输出：通过 getOSStaticInfo() 接口将采集到的信息封装为标准 JSON 格式，便于外部模块（如 MQTT 客户端）上报
 * @note 1. 依赖组件：OpenHarmony 系统 API（syspara/parameter、ss_mpi_sys、hal_sys_param）、hilog 日志、cJSON 解析库、securec 安全函数
 *       2. 配置文件路径：系统参数文件（ohos.para）、网络配置文件（S80network）路径已固化，需确保目标设备路径一致
 *       3. 线程安全：通过 pthread_rwlock 读写锁保护全局缓存标识和数据，支持多线程环境调用
 *       4. 采集失败处理：单个信息采集失败会导致全局缓存标识为 false，但仍会返回已采集到的有效信息
 * @attention 1. 需确保目标设备具备相关文件（ohos.para、S80network）的读取权限，以及系统命令（ifconfig、cat、df 等）的执行权限
 *            2. 设备序列号采集依赖芯片硬件接口，不同芯片平台可能需要适配 ss_mpi_sys 接口或 SN 读取路径
 *            3. JSON 输出接口（getOSStaticInfo()）要求传入非空 cJSON 对象，调用前需提前创建
 *            4. 内存与磁盘大小单位为 KB，IP 地址仅采集 eth0 网卡（适用于有线网络场景）
 */
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdbool.h>
#include <cJSON.h>
#include "securec.h"
#include "ohos_types.h"
#include "hilog/log.h"
#include "syspara/parameter.h"

#include "ss_mpi_sys.h"
#include "hal_sys_param.h"

static bool g_bPickupFlag = false;
static pthread_rwlock_t g_rwlock = PTHREAD_RWLOCK_INITIALIZER;

static const char *OHOSFile = "/system/etc/param/ohos_const/ohos.para";
static const char *NetFile = "/etc/init.d/S80network";

#define MaxLen 256
#define maxLineSize 1024
typedef struct OSStaticData {
    char osVersion[MaxLen];
    char chip[MaxLen];
    char sn[MaxLen];
    char ip[MaxLen];
    int  cpuNum;
    int  ram; // mem size kB
    int  diskSpace; // disk size kB
}OSStaticData;

static OSStaticData osInfos = {0};

static bool isPickuped(void)
{
    pthread_rwlock_rdlock(&g_rwlock);
    bool rst = g_bPickupFlag;
    pthread_rwlock_unlock(&g_rwlock);
    return rst;
}

char *pickTagVal(char *str, const char *delims)
{
     char *rst = strtok(str, delims);
     if (rst == NULL) {
         return str;
     }

     rst = strtok(NULL, delims);
     if (rst == NULL) {
         return str;
     }
     return rst;
}

bool getLineTagInfo(char *line, char *saveStr, const int savelen, const char *tag)
{
    if (memcmp(line, tag, strlen(tag)) != 0) {
        return false;
    }

    char *val = pickTagVal(line, "=");
    if (strcpy_s(saveStr, savelen, val) != EOK) {
        return false;
    }
    
    HILOG_INFO(LOG_CORE, "[%s]:[%s]", tag, val);
    return true;
}

static void trimRight(char *line)
{
    int len = strlen(line);
    if (len > 0 && line[len-1] == '\n') {
        line[len-1] = '\0';
    }
}

static bool pickupOSVerFromAPI(char *ver, size_t len)
{
    if (ver == NULL) {
        return false;
    }

    const char *sfm = GetSoftwareModel();
    if (sfm != NULL) {
        int err = strcpy_s(ver, len, sfm);
        if (err == EOK) {
            return true;
        }
    }
    HILOG_ERROR(LOG_CORE, "get os version from api fail.");
    return false;
}

static bool pickupTagValFromFile(
        const char *fileNm, const char *tag, 
        char *value, size_t valLen)
{
    FILE *fp = fopen(fileNm, "r");
    if (fp == NULL) {
        HILOG_ERROR(LOG_CORE, "open file(%s) fail", fileNm);
        return false;
    }

    char line[maxLineSize] = {'\0'};
    while(!feof(fp)) {
        if (fgets(line, sizeof(line), fp) == NULL) {
            return false; 
        }
        trimRight(line);

        if (getLineTagInfo(line, value, valLen, tag)) {
            return true;
        }
    }
    return false;
}

static bool pickupOSVer(char *ver, size_t verLen)
{
    if (pickupOSVerFromAPI(ver, verLen)) {
        return true;
    }

    return pickupTagValFromFile(OHOSFile, "const.ohos.fullname", ver, verLen);
}

static bool execCmd(const char *cmd, char *rst, size_t maxLen)
{
    if (rst == NULL || cmd == NULL) {
        HILOG_ERROR(LOG_CORE, "execCmd fail, please check params.");
        return false;
    }

    char output[maxLineSize] = {0};
    do
    {
        FILE *fp = popen(cmd, "r");
        if(fp == NULL) {
            HILOG_ERROR(LOG_CORE, "popen fail:[%s]", cmd);
            break;
        }

        if(fgets(output, sizeof(output), fp) != NULL) {
            HILOG_INFO(LOG_CORE, "cmd rst: %s", rst);
        }
        pclose(fp);
    }while(false);

    // xxxxx\n
    if (strlen(output) < 2) {
        return false;
    }
    trimRight(output);

    int err = strcpy_s(rst, maxLen, output);
    if (err != EOK) {
        HILOG_ERROR(LOG_CORE, "strcpy fail: %d", err);
        return false;
    }
    return true;
}

static bool pickupIpFromCmd(char *ip, size_t len)
{
    const char *cmd = "ifconfig eth0 | grep 'inet addr' | cut -d':' -f2 | cut -d' ' -f1"; 
    return execCmd(cmd, ip, len);
}

static bool pickupIp(char *ip, size_t len)
{
    if (pickupTagValFromFile(NetFile, "ipaddr", ip, len)) {
        return true;
    }
    return pickupIpFromCmd(ip, len);
}

static bool pickupChip(char *chip, size_t len)
{
    const char *cmd = "uname -m"; 
    return execCmd(cmd, chip, len);
}

// /sys/block/mmcblk0/device/cid 
static bool pickDevSNFromFile(char *sn, int maxLen) {
    const char * val = HalGetSerial();
    if (val == NULL || strcmp(val, "1234567890") == 0) {
        return false;
    }

    (void)memset_s(sn, maxLen, 0, maxLen);
    (void)memcpy_s(sn, maxLen, val, strlen(val));
    HILOG_INFO(LOG_CORE, "pick sn from file: %s", sn);
    return true;
}

// SN is 20 bytes, so hex format is 40 bytes.
static bool pickDevSNFromMPI(char *sn, int maxLen)
{
    if (maxLen < 50) {
        return false;
    }

    memset_s(sn, maxLen, 0, maxLen);

    //td_s32 r = ss_mpi_sys_init(); // after testing, no need to execute
    ot_unique_id *id = (ot_unique_id *)malloc(sizeof(ot_unique_id));
    td_s32 rst = ss_mpi_sys_get_unique_id(id);
    for (int i=0; i<OT_UNIQUE_ID_NUM; i++) {
        printf("----sn test [%d]: [%08x]----\n", i, id->id[i]);
    }
    //r = ss_mpi_sys_exit(); // init no need to execute, so exit too.

    bool existValidVal = false;
    for (int i=0; i<5; i++) {
        snprintf_s(&sn[8*i], 9, 8, "%08x", id->id[i]);
        if (id->id[i] != 0) {
            existValidVal = true;
        }
    }
    free(id);

    return existValidVal ? true:false;
}

static bool pickDevSN(char *sn, int maxLen) {
    if (pickDevSNFromMPI(sn, maxLen)) {
        return true;
    }

    return pickDevSNFromFile(sn, maxLen);
}

static bool pickupCpuNum(int *num)
{
    const char *cmd = "cat /proc/cpuinfo | grep processor | wc -l"; 
    char cpuNum[MaxLen] = {0};
    bool rst = execCmd(cmd, cpuNum, sizeof(cpuNum));
    if (!rst) {
        return false;
    }
    
    *num = atoi(cpuNum);
    return true;
}

static bool pickupRam(int *ram)
{
    const char *cmd = "cat /proc/meminfo | grep MemTotal | cut -d':' -f2"
        "| sed -e 's/^[ \t]*//g' | cut -d' ' -f1"; 
    char val[MaxLen] = {0};
    bool rst = execCmd(cmd, val, sizeof(val));
    if (!rst) {
        return false;
    }
    
    *ram = atoi(val);
    return true;
}

static bool pickupDiskSpace(int *disk)
{
    const char *cmd = "arr=($(df -k | grep -v '^Filesystem' | sed -r 's/[ ]+/|/g' "
        "| cut -d'|' -f2));sum=0;for i in \"${arr[@]}\"; do sum=$((sum + i )); done; echo $sum";
    char val[MaxLen] = {0};
    bool rst = execCmd(cmd, val, sizeof(val));
    if (!rst) {
        return false;
    }
    
    *disk = atoi(val);
    return true;
}

/* data sample
// {
//    "os_version": "OpenHarmony-4.0.10.13",
//    "chip"      : "aarch64",
//    "sn"        : "57440d742282c81855d6561751d8030a00035e4f",
//    "ip"        : "x.x.x.x",
//    "cpu_num"   : 2,
//    "ram"       : 104316,
//    "rom"       : 146348,
// }
*/

static void packageDataToJsonFrm(cJSON *rst)
{
    cJSON_AddStringToObject(rst, "os_version", osInfos.osVersion);
    cJSON_AddStringToObject(rst, "chip", osInfos.chip);
    cJSON_AddStringToObject(rst, "sn", osInfos.sn);
    cJSON_AddStringToObject(rst, "ip", osInfos.ip);
    cJSON_AddNumberToObject(rst, "cpu_num", osInfos.cpuNum);
    cJSON_AddNumberToObject(rst, "ram", osInfos.ram);
    cJSON_AddNumberToObject(rst, "rom", osInfos.diskSpace);
}

//  
void getOSStaticInfo(cJSON *rst)
{
    if (rst == NULL) {
        HILOG_ERROR(LOG_CORE, "getOSStaticInfo param is NULL");
        return;
    }

    if (!isPickuped()) {
        pthread_rwlock_wrlock(&g_rwlock);
        if (!g_bPickupFlag) {
            HILOG_INFO(LOG_CORE, "only once perform, first time.");

            bool rVer  =  pickupOSVer(osInfos.osVersion, sizeof(osInfos.osVersion));
            bool rChip =  pickupChip(osInfos.chip, sizeof(osInfos.chip));
            bool rSN   =  pickDevSN(osInfos.sn, sizeof(osInfos.sn));
            bool rIp   =  pickupIp(osInfos.ip, sizeof(osInfos.ip));
            bool rCpu  =  pickupCpuNum(&osInfos.cpuNum);
            bool rRam  =  pickupRam(&osInfos.ram);
            bool rDisk =  pickupDiskSpace(&osInfos.diskSpace);

            g_bPickupFlag = rVer && rChip && rSN && rIp && rCpu && rRam && rDisk;
        }
        pthread_rwlock_unlock(&g_rwlock);
    }
    
    packageDataToJsonFrm(rst);
}
